package elevator;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

public class Controller{
	
	protected MasterControllerThread master;
	protected Map<Integer, ElevatorThread> elevators;
	
	protected Socket socket;
	public double velocity;
	public int numberOfElevators;
	public int numberOfFloors;
	public static writeToSocketThread writer;
	public BufferedReader in;
	
	public static void main(String args[]){
		writer = new writeToSocketThread();
		writer.start();
		Controller c = new Controller();
	}
	
	public Controller() {
		velocity = 0;
		
		try {
			socket = new Socket("localhost", 4711);
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		elevators = new HashMap<Integer, Controller.ElevatorThread>();
		writer.sendToElevators("i");
		
		String[] array = null;
		try {
			array = in.readLine().split(" ");
		} catch (IOException e) {
			e.printStackTrace();
		}

		numberOfElevators = Integer.parseInt(array[1]);
		numberOfFloors = Integer.parseInt(array[2]);
		
		System.out.println("Controller reads " + numberOfElevators + " elevators and " + numberOfFloors + " floors!");
		
		master = new MasterControllerThread();
		master.start();
		
		for(int i = 0; i < numberOfElevators; i++){
			elevators.put(i, new ElevatorThread(i+1));
		}
	}

	public class MasterControllerThread extends Thread{
	
		@Override
		public void run() {
			
			String inStream = "";
			String[] array = null;
			char command;
			int elevatorNumber = 0;
			int floorNumber;
			double value2;
			
			try {
				while (true) {
					//Read input
					inStream = in.readLine();
					
					array = inStream.split(" ");
					command = array[0].toCharArray()[0];
					
					//Parse
					if(command == 'v'){
						value2 = Double.parseDouble(array[1]);
						velocity = value2;
						continue;
					}
					
					if(command  == 'b'){
						floorNumber = Integer.parseInt(array[1]);
						int directionOfCall = Integer.parseInt(array[2]);
						
						//Find the correct elevator.
						elevatorNumber = getClosestElevator(floorNumber,directionOfCall);
						
						if(elevatorNumber < 1) elevatorNumber = 1;
					}
					else{
						elevatorNumber = Integer.parseInt(array[1]);
					}
					
					//send the command to the correct elevator
					elevators.get(elevatorNumber-1).que.put(inStream);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/**
		 * Gets the closest, currently unused, elevator from a specific floor. 
		 * @return
		 */
		public int getClosestElevator(int floorNumber, int directionOfCall){
			
			boolean goingUp = false;
			double closest = Integer.MAX_VALUE;
			double currentFloor = -1.0, dist = -1;
			int bestId = -1, currentId = -1;
			
			if(numberOfElevators == 1) return 1;
			
			//If there is a idle elevator in this floor.
			for(int i = 0; i < numberOfElevators; i++){
				if(elevators.get(i).alg.isIdle() && elevators.get(i).moving == 0){
					currentFloor = elevators.get(i).alg.getCurrentFloor();
					if(currentFloor == floorNumber){
						bestId = elevators.get(i).elevatorNumber;
						System.out.println("Elevator #"+bestId+" was already at " +floorNumber);
						return bestId;
					}
				}
			}
			
			//Check for already moving elevators in the correct direction.
			for(int i = 0; i < numberOfElevators; i++){
				
				currentFloor = elevators.get(i).currentPosition;
				currentId = elevators.get(i).elevatorNumber;
				
				//if its not moving, discard it.
				if(elevators.get(i).moving == 0){
					continue;
				}
				
				if(directionOfCall == 1)goingUp = true;
				else goingUp = false;
				
				if(elevators.get(i).alg.movingUp() != goingUp)
					continue;
				
				//Check for bad conditions. 
				//If higher and moving up.
				if((currentFloor > floorNumber) && (elevators.get(i).moving == 1))
					continue;
				//If lower and moving down.
				if((currentFloor < floorNumber) && (elevators.get(i).moving == -1))
					continue;
				
				dist = Math.abs(currentFloor - floorNumber);
				if(dist < closest){ 
					closest = dist;
					bestId = currentId;
				}
			}
			if(bestId == -1) {
				//Found no good moving elevators. Take the closest idle one
				for(int i = 0; i < numberOfElevators; i++){
					
					currentFloor = elevators.get(i).currentPosition;
					currentId = elevators.get(i).elevatorNumber;
					
					//if its not moving, discard it.
					if(elevators.get(i).moving != 0 || elevators.get(i).alg.isIdle() == false){
						continue;
					}
					
					dist = Math.abs(currentFloor - floorNumber);
					if(dist <= closest){ 
						closest = dist;
						bestId = currentId;
					}
				}
			}
			
			if(bestId == -1){
				Random r = new Random(System.currentTimeMillis());
				bestId = r.nextInt(numberOfElevators);
			}
			
			System.out.println("closest to " + floorNumber +" was Elevator#" + bestId);
			if(bestId == -1) System.out.println("WARNING: Closest elevator return -1");
			return bestId;
		}
	}
	
	public class ElevatorThread extends Thread{
		
		//The class that decides and holds the planning for this elevator
		public ElvAlg alg;
		
		//this elevators number.
		public int elevatorNumber;
		
		//Simple boolean for if the elevator is moving or not and in what direction.
		//	1 = UP
		//	0 = STILL
		//	-1 = DOWN
		public int moving = 0;
		
		//The floor this elevator is currently moving towards.
		public int floorGoal = 0;
		
		//The floor the elevator is currently on.
		public double currentPosition = 0;
		
		//Is the doors open?
		public boolean isDoorsOpen = false;
		
		public LinkedBlockingQueue<String> que;

		public ElevatorThread(int number){
			que = new LinkedBlockingQueue<String>();
			elevatorNumber = number;
			alg = new ElvAlg();
			
			this.start();
		}
		
		@Override
		public void run() {
			String input = null;
			String[] array;
			char command;
			int value1;
			int floorNumber;
			double value2;
			while(true){
				try {
					input = que.take();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				array = input.split(" ");
				command = array[0].toCharArray()[0];
				
				if(command == 'b'){
					value1 = Integer.parseInt(array[2]);
					floorNumber = Integer.parseInt(array[1]);
					getWhatToDo(command, floorNumber, value1);
				}
				else if(command == 'p'){
					value1 = Integer.parseInt(array[2]);
					getWhatToDo(command, value1, null);
				}
				else if(command == 'd'){
					value1 = Integer.parseInt(array[2]);
					operateDoors(value1);
				}
				else if(command == 'f'){
					value2 = Double.parseDouble(array[2]);
					
					setCurrentPosition(value2);
				}
			}
		}
		
		public void getWhatToDo(char command, int value1, Object value2){
			int floor = 0;
			int direction = 0;
			boolean goingUp = false;
			boolean stop = false;
			
			if(command == 'b'){
				floor =  value1;
				direction = (Integer) value2;
				if(direction == 1) goingUp = true;
				else goingUp = false;
				
				//if the elevator is idle and on the correct floor allready.
				if(floor == alg.getCurrentFloor() && alg.isIdle()){
					OpenAndCloseDoors();
				}
				else{
					System.out.println("call floor:"+ floor +",direction: " +direction+ ", goingUp:" + goingUp);
					alg.call(floor, goingUp);
				}
			}
			else if(command == 'p'){
				floor =  value1;
				
				if(floor == 32000){
					System.out.println("CLEARING! Elevator #" + elevatorNumber);
					
					moving = 0;
					alg.clear();
					//floorGoal = alg.getCurrentFloor();
					stopElevator();
					stop = true;
				}else{
					if(floor == alg.getCurrentFloor() && alg.isIdle()){
						OpenAndCloseDoors();
					}
					else{
						System.out.println("FloorPressed to:" + floor);
						alg.floorPressed(floor);
					}
				}
			}
			if(!stop){
				int nextStop = alg.nextStop();
				
				if(floorGoal != nextStop){
					floorGoal = nextStop;
					moveToFloor(floorGoal);
				}
			}
		}
		
		public void setCurrentPosition(double d) {
			
			int floorElevatorIsOn = (int) (d+0.5);
			currentPosition = d;
			
			double dif = Math.abs((double)floorGoal - d);
			
			if(dif <= 0.045){
				//WE ARE AT A FLOOR WE NEED TO STOP AT!
				moving = 0;
				alg.stops(floorElevatorIsOn);
				
				stopElevator();
				
				OpenAndCloseDoors();
				
				floorGoal =  alg.nextStop();
				if(floorGoal != -1){
					moveToFloor(floorGoal);
				}
				System.out.println("Elevator #"+elevatorNumber+" is stopping at "+ floorElevatorIsOn +", floorGoal: " + floorGoal);
				if(!alg.isIdle())alg.printInfo();
			}
			else{
				alg.movingPast(floorElevatorIsOn);
			}
		}

		private void OpenAndCloseDoors() {
			openDoors();
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			closeDoors();
		}
		
		public void moveToFloor(int floorNumber){
			if(floorNumber < 0 || floorNumber > numberOfFloors){
				System.out.println("Cant move elevator #"+elevatorNumber+" to floor: " +floorNumber);
			}
			
			//floorGoal = floorNumber;
			int direction = 0;
			if(currentPosition > floorNumber) direction = -1;
			else if(currentPosition < floorNumber) direction = 1;
			else direction = 0;
			
			moveElevator(direction);
		}
		
		//m elevatorNumber direction
		public void moveElevator(int direction){
			String output = "m " + elevatorNumber + " " + direction;
			//System.out.println("Controller: " + output+"!");
			moving = direction;
			writer.sendToElevators(output);
		}
		
		//m elevatorNumber 0;
		public void stopElevator(){
			moveElevator(0);
		}
		
		//w elevatorNumber
		public void getElevatorPosition(){
			writer.sendToElevators("w " + elevatorNumber);
		}
		
		public void operateDoors(int input){
			if(input == 1)
				openDoors();
			else if(input == -1)
				closeDoors();
		}
		
		//d elevatorNumber 1
		public void openDoors(){
			isDoorsOpen = true;
			writer.sendToElevators("d " + elevatorNumber + " 1");
		}
		
		//d elevatorNumber -1
		public void closeDoors(){
			isDoorsOpen = false;
			writer.sendToElevators("d " + elevatorNumber + " -1");
		}
	}
}
